//
// File  : FlightsController.cpp
// Team  : Dangling Pointers
// Class : CST236 - Software Testing
//
// FlightsController implementation.
//

#include "FlightsController.h"
#include "Flight.h"
#include "Global.h"
#include <iostream>
#include <cstdlib>
#include <sstream>
#include <string>
#include <cctype>


FlightsController::FlightsController(FlightDatabase* database) : m_database(database)
{
	displayMenu();
}

//
// displayMenu : void
//
// method will display the menu for the FlightsController and then prompt for user
// input corresponding to menu choice.
//
void FlightsController::displayMenu()
{
	while (true) 
	{
		clearConsole();

		std::string menuName = "Manage Flight Menu";
		std::cout << std::endl << "====== " << menuName << " ======" << std::endl;
		std::cout << "1. Add flight." << std::endl;
		std::cout << "2. Delete flight." << std::endl;
		std::cout << "3. Update flight." << std::endl;
		std::cout << "4. Show flights." << std::endl;
		std::cout << "5. Return to main menu." << std::endl;

		int input = getUserInput(5);

		switch(input)
		{
		case 1:
			clearConsole();
			addFlight();
			pressEnterToContinue();
			break;

		case 2:
			clearConsole();
			deleteFlight();
			pressEnterToContinue();
			break;

		case 3:
			clearConsole();
			updateFlight();
			pressEnterToContinue();
			break;

		case 4:
			clearConsole();
			m_database->printFlights();
			pressEnterToContinue();
			break;

		default:
			return;
		}
	}
}

//
// addFlight : void
//
// method will prompt the user to enter a flight number in a specified format,
// as well as departure and arrival times in a specified format. The method 
// will then prompt the user if they would like to add the flight to the
// database.
//
void FlightsController::addFlight() 
{
	std::cout << std::endl << "====== Add Flight ======" << std::endl;
	std::string flightNumber;
	std::string input;

	// get flight number
	while (true) 
	{
		getFlightFromUser(flightNumber);

		bool flightFound;

		try 
		{
			m_database->getFlight(flightNumber);
			flightFound = true;
		} 
		catch (std::exception) 
		{
			flightFound = false;
		}

		if (flightFound == true)
		{
			std::cout << "Flight already in database!" << std::endl;
			continue;
		}
		if ( validateFlightNumber(flightNumber) ) 
		{
			break;
		}


		std::cout << "Invalid entry! ";
	}

	// get departure and arrival times
	std::string departure = getFlightTime("Enter departure time (ex 0845)");
	std::string arrival = getFlightTime("Enter arrival time (ex 0845)");

	// print flight information
	std::cout << "Flight Number   : " << flightNumber << std::endl;
	std::cout << "Flight Departure: " << departure << std::endl;
	std::cout << "Flight Arrival  : " << arrival << std::endl;
	std::cout << "Flight Status   : On-time" << std::endl;
	std::cout << "Save flight (y/n)? : ";

	getline(std::cin, input);

	// prompt user if they want to save the current flight info
	if (input == "y") 
	{
		m_database->addFlight(flightNumber, Flight(flightNumber, departure,
			arrival, Flight::ONTIME));
		std::cout << "Flight Saved." << std::endl << std::endl;
	} 
	else 
	{
		std::cout << "Flight Not Saved." << std::endl << std::endl;
	}
}

//
// deleteFlight : void
//
// method will prompt the user for a flight number to be removed from the
// database. If the flight they entered does not exist, an error message
// will be displayed on the console and they will be prompted again to enter
// another flight, or to hit nothing to cancel.
//
void FlightsController::deleteFlight() 
{
	std::cout << std::endl << "====== Delete Flight ======" << std::endl;
	m_database->printFlights();
	std::string flightNumber;

	while (true)
	{
		std::cout << "Enter a flight number to remove or nothing to cancel: ";
		getline(std::cin, flightNumber);

		if(flightNumber.size() == 0) 
			return;

		if (m_database->removeFlight(flightNumber)) 
		{
			std::cout << "Flight removed." << std::endl << std::endl;
			break;
		}

		std::cout << "Invalid entry! ";
	}
}

//
// updateFlight : void
//
// method will prompt the user to enter a flight number or hit nothing to cancel.
// If the user enters a flight that is not found, the user will again be prompted
// for another flight number. Otherwise, the user will be able to update all of the
// fields for the flight specified.
//
void FlightsController::updateFlight() 
{
	std::cout << std::endl << "====== Update Flight ======" << std::endl;
	m_database->printFlights();
	bool flightFound = true;
	std::string flightNumber;

	while (true) 
	{
		std::cout << "Enter a flight number to update or nothing to cancel: ";
		getline(std::cin, flightNumber);

		if (flightNumber.size() == 0) 
			return;

		try 
		{
			m_database->getFlight(flightNumber);
			flightFound = true;
		} 
		catch (std::exception) 
		{
			flightFound = false;
		}

		if (flightFound) 
			break;

		std::cout << "Invalid entry! ";
	}

	// get new departure time from user, validate input, then update in database
	std::string departure = getFlightTime("Enter new departure time (in 2400 format) or nothing to keep previous",
		true);
	if (departure.size() != 0) 
		m_database->changeFlightDepartureTime(flightNumber, departure);

	// get new arrival time from user, validate input, then update database
	std::string arrival = getFlightTime("Enter arrival time (in 2400 format) or nothing to keep previous",
		true);
	if (arrival.size() != 0) 
		m_database->changeFlightArrivalTime(flightNumber, arrival);

	std::cout << "1. On time" << std::endl;
	std::cout << "2. Delayed" << std::endl;
	std::cout << "3. Canceled" << std::endl;
	std::cout << "4. Do not change" << std::endl;

	int input = getUserInput(4);

	if (input == 1) 
		m_database->changeFlightStatus(flightNumber, Flight::ONTIME);
	if (input == 2) 
		m_database->changeFlightStatus(flightNumber, Flight::DELAYED);
	if (input == 3) 
		m_database->changeFlightStatus(flightNumber, Flight::CANCELLED);

	std::cout << "Flight has been updated." << std::endl << std::endl;
}


//
// getFlightTime : string
// 
// method takes a string representing the prompt and a boolean representing empty input.
// it will determine if the flight time the user entered is valid or not.
//
std::string FlightsController::getFlightTime(std::string prompt, bool tackEmptyInput)
{
	std::string input;

	while (true) 
	{
		std::cout << prompt << ": ";
		getline(std::cin, input);

		//
		// What is the purpose of tackEmptyInput variable? Wouldn't checking for
		// an input size of zero be sufficient?
		//
		if (tackEmptyInput && input.size() == 0) 
			return input;

		if(validateTime(input))
			return input;

		std::cout << "Invalid entry! ";
	}
}


//
// validateTime : bool
//
// Method takes a string representing the time and returns a boolean value.
// The method determines whether or not the time passed by the user is in the 
// correct 2400 format.
//
bool FlightsController::validateTime(std::string time)
{	
	// check if the string length is 4
	if(time.size() != 4)
		return false;

	//
	// make sure remaining three entries in the string are digits!
	//
	for(size_t i = 1; i < time.length(); ++i)
	{
		if( !isdigit(time[i]) )
			return false;
	}

	//
	// TODO: validate that flight time is not above 2359!
	//
	int fullTimeToInt = atoi(time.c_str());
	if(fullTimeToInt > 2359) return false;

	//
	// convert each "digit" in the 2400 time string to component
	// integers for validation purposes
	//
	int hourDigit1   = atoi(time.substr(0,1).c_str());
	int hourDigit2   = atoi(time.substr(1,1).c_str());
	int minuteDigit1 = atoi(time.substr(2,1).c_str());
	int minuteDigit2 = atoi(time.substr(3,1).c_str());


	if( !(hourDigit1 > -1 && hourDigit1 < 3) )
		return false;
	if( (hourDigit1 == 2) && !(hourDigit2 > -1 && hourDigit2 < 5) )
		return false;
	if( !(hourDigit2 > -1 && hourDigit2 < 10) )
		return false;
	if( !(minuteDigit1 > -1 && minuteDigit1 < 6) )
		return false;
	if( !(minuteDigit2 > -1 && minuteDigit2 < 10) )
		return false;

	return true;
}
